package com.whiteape.book.core.manager;

import com.whiteape.book.common.WaConstants;
import com.whiteape.book.core.parser.CategoryParser;
import com.whiteape.book.core.parser.HomepageParser;
import com.whiteape.book.core.parser.MediaInfoParser;
import com.whiteape.book.core.parser.ChapterContentParser;
import com.whiteape.book.core.parser.CatalogParser;
import com.whiteape.book.core.parser.SearchResultParser;
import com.whiteape.book.model.bo.CategoryDetailBo;
import com.whiteape.book.model.bo.ChapterContentBo;
import com.whiteape.book.model.bo.SearchResultBo;
import com.whiteape.book.model.dto.CollectionMediaBean;
import com.whiteape.book.model.dto.MediaChapterBean;
import com.whiteape.book.model.dto.SiteSourceBean;
import com.whiteape.book.model.dto.SiteSourceCategoryBean;
import com.whiteape.book.model.dto.SiteSourceCategoryRuleBean;
import com.whiteape.book.model.dto.SiteSourceHomepageBean;
import com.whiteape.book.model.dto.SiteSourceSearchBean;
import com.whiteape.book.model.type.MediaType;
import com.whiteape.book.net.RetrofitHelper;
import com.whiteape.book.utils.DaoDbHelper;
import com.whiteape.book.utils.LogUtils;
import com.whiteape.book.utils.URLUtils;

import org.apache.commons.lang3.StringUtils;

import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import okhttp3.RequestBody;

/**
 * 网络书籍管理器
 */
public class WebMediaManager {
    private static WebMediaManager instance;
    private Executor executor = Executors.newFixedThreadPool(WaConstants.FIX_THREAD_NUM);
    private Scheduler scheduler = Schedulers.from(executor);

    private WebMediaManager() {
    }

    public static WebMediaManager getInstance() {
        if (instance == null) {
            instance = new WebMediaManager();
        }
        return instance;
    }


    public Observable<List<SearchResultBo>> getSearchInfo(String searchKey) {
        return getSearchInfoWithMediaType(searchKey, MediaType.MEDIA_TYPE_ALL);
    }

    /**
     * 获取搜索结果，先拿到数据源，然后每个数据源都来一个搜索，合并成observable返回，
     * 每次的数据在onNext中给出
     *
     * @param searchKey
     * @return
     */

    public Observable<List<SearchResultBo>> getSearchInfoWithMediaType(String searchKey, int mediaType) {


        List<SiteSourceBean> siteSourceBeanList;
        if (mediaType == MediaType.MEDIA_TYPE_ALL) {
            siteSourceBeanList = SiteSourceManager.getInstance().getSiteSourceAll();
        } else {
            siteSourceBeanList = SiteSourceManager.getInstance().getSiteSourceWithMediaType(mediaType);
        }

        List<Observable<List<SearchResultBo>>> searchObservableList = new ArrayList<>(siteSourceBeanList.size());

        for (SiteSourceBean siteSourceBean : siteSourceBeanList) {

            if (siteSourceBean == null || StringUtils.isEmpty(searchKey)) {
                return Observable.create(emitter -> emitter.onError(new Throwable("getSearchInfo:siteSourceBean is null or searchKey is empty")));
            }

            SiteSourceSearchBean searchBean = siteSourceBean.getSearch();
            String baseUrl = siteSourceBean.getSiteHostUrl();
            String searchUrl = searchBean.getUrl().trim();


            Map<String, String> params = searchBean.getParams();
            if (null == params) {
                params = new HashMap<>(1);
            }
            Map<String, String> tempParams = new HashMap<>(params.size());
            try {
                String encodedSearchKey = URLEncoder.encode(searchKey, searchBean.getCharset());


                //支持resultful 形式的api
                //https://www.qijizuopin.com/search/%E5%A4%A9%E4%B8%8B
                if (searchUrl.contains("{{")) {
                    Map<String, String> searchMap = new HashMap<>();
                    searchMap.put("searchKey", encodedSearchKey);
                    searchUrl = ParserManager.parseOne(searchUrl, searchUrl, searchUrl, searchMap, true);
                }

                for (Map.Entry<String, String> entry : params.entrySet()) {
                    if ("searchKey".equals(entry.getValue().trim())) {
                        tempParams.put(entry.getKey(), encodedSearchKey);
                    } else {
                        tempParams.put(entry.getKey(), entry.getValue());
                    }
                }
                Map<String, String> headers = searchBean.getHeaders();
                if (headers == null) {
                    headers = new HashMap<>();
                    headers.put("Referer", siteSourceBean.getSiteHostUrl());
                } else if (!headers.containsKey("referer") && !headers.containsKey("Referer")) {
                    headers.put("Referer", siteSourceBean.getSiteHostUrl());

                }
                //TODO cancel task?
                if (searchBean.getRequestMethod() != null && "POST".equals(searchBean.getRequestMethod().toUpperCase())){

                    Observable<List<SearchResultBo>> observable = RetrofitHelper.getInstance().getHttpApiImpl(baseUrl)
                            .postMap(searchUrl, tempParams, headers)
                            .timeout(60, TimeUnit.SECONDS)
                            .subscribeOn(scheduler)
                            .flatMap(stringResponse -> SearchResultParser.parse(siteSourceBean, stringResponse.raw().request().url().toString(), stringResponse.body()))
                            .toObservable();
                    searchObservableList.add(observable);
                }else {
                    Observable<List<SearchResultBo>> observable = RetrofitHelper.getInstance().getHttpApiImpl(baseUrl)
                            .getMap(searchUrl, tempParams, headers)
                            .timeout(60, TimeUnit.SECONDS)
                            .subscribeOn(scheduler)
                            .flatMap(stringResponse -> SearchResultParser.parse(siteSourceBean, stringResponse.raw().request().url().toString(), stringResponse.body()))
                            .toObservable();
                    searchObservableList.add(observable);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return Observable.create(emitter -> emitter.onError(new Throwable(e)));
            }
        }
        return Observable.mergeDelayError(searchObservableList);
    }


    public Single<CollectionMediaBean> getMediaInfo(CollectionMediaBean collectionMediaBean) {

        collectionMediaBean.__setDaoSession(DaoDbHelper.getInstance().getSession());
        SiteSourceBean siteSourceBean = collectionMediaBean.getSiteSourceBean();
        String mediaDetailUrl = collectionMediaBean.getId();

        LogUtils.d("getMediaInfo siteSourceBean = " + siteSourceBean + " bookDetailUrl = " + mediaDetailUrl);
        if (siteSourceBean == null || StringUtils.isEmpty(mediaDetailUrl)) {
            return Single.create(emitter -> emitter.onError(new Throwable("getMediaInfo: siteSourceBean is null or bookDetailUrl is empty")));
        }
        String charset = siteSourceBean.getSearch().getCharset();

        String baseUrl = siteSourceBean.getSiteHostUrl();
        Map<String, String> headers = siteSourceBean.getMediaInfoDetailRule().getHeaders();
        try {
            if (headers == null) {
                headers = new HashMap<>();
                headers.put("Referer", URLEncoder.encode(mediaDetailUrl, charset));
            } else if (!headers.containsKey("referer") && !headers.containsKey("Referer")) {
                headers.put("Referer", URLEncoder.encode(mediaDetailUrl, charset));
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        try {
            return RetrofitHelper.getInstance().getHttpApiImpl(baseUrl)
                    .get(mediaDetailUrl, headers)
                    .subscribeOn(scheduler)
                    .timeout(30, TimeUnit.SECONDS)
                    .flatMap(stringResponse -> MediaInfoParser.parse(collectionMediaBean, stringResponse.body()));


        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.e("getMediaInfo With Exception");
            return Single.create(emitter -> emitter.onError(new Throwable(e)));
        }

    }

    public Single<List<MediaChapterBean>> getCatalog(SiteSourceBean siteSourceBean, CollectionMediaBean collectionMediaBean) {
        String chapterListUrl = collectionMediaBean.getCatalogUrl();
        if (chapterListUrl == null) {
            //某种原因，并没有书籍的详情信息，没有章节规则，那么先去获取章节规则吧
            return WebMediaManager.getInstance().getMediaInfo(collectionMediaBean)
                    .timeout(30, TimeUnit.SECONDS)
                    .retry(3)
                    .flatMap(mediaBean -> {
                        if (mediaBean != null) {
                            mediaBean.__setDaoSession(DaoDbHelper.getInstance().getSession());
                        }
                        if (mediaBean != null && mediaBean.getCatalogUrl() != null) {
                            //解析详情的时候，经常目录也在一个页面，同时解析了，这样避免两次请求解析
                            if (mediaBean.getMediaChapterList() != null && !mediaBean.getMediaChapterList().isEmpty()) {
                                collectionMediaBean.setCoverUrl(mediaBean.getCoverUrl());
                                collectionMediaBean.setLastUpdated(mediaBean.getLastUpdated());
                                collectionMediaBean.setDesc(mediaBean.getDesc());
                                collectionMediaBean.setAuthor(mediaBean.getAuthor());
                                collectionMediaBean.setWordCount(mediaBean.getWordCount());
                                collectionMediaBean.setState(mediaBean.getState());
                                collectionMediaBean.setLastChapter(mediaBean.getLastChapter());
                                collectionMediaBean.setCatalogUrl(mediaBean.getCatalogUrl());
                                collectionMediaBean.setCatalogNextPageUrl(mediaBean.getCatalogNextPageUrl());
                                collectionMediaBean.setMediaChapterList(mediaBean.getMediaChapterList());
                                return Single.just(mediaBean.getMediaChapterList());
                            }
                            return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                                    .get(mediaBean.getCatalogUrl(), Collections.emptyMap())
                                    .retry(3)
                                    .timeout(30, TimeUnit.SECONDS)
                                    .subscribeOn(scheduler)
                                    .flatMap(stringResponse -> CatalogParser.parse(siteSourceBean, mediaBean, stringResponse.body()));
                        } else {
                            return Single.create(emitter -> emitter.onError(new Throwable("未知错误啊")));
                        }
                    });

        }
        if (siteSourceBean == null || StringUtils.isEmpty(chapterListUrl)) {
            return Single.create(emitter -> emitter.onError(new Throwable("getCatalog : siteSourceBean is null or bookDetailUrl or chapterListUrl is empty")));
        }

        try {
            return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                    .get(chapterListUrl, Collections.emptyMap())
                    .timeout(30, TimeUnit.SECONDS)
                    .subscribeOn(scheduler)
                    .flatMap(stringResponse -> CatalogParser.parse(siteSourceBean, collectionMediaBean, stringResponse.body()));

        } catch (Exception e) {
            return Single.create(emitter -> emitter.onError(e));
        }
    }

    public Single<List<ChapterContentBo>> getBookChapterContent(SiteSourceBean siteSourceBean, String chapterDetailUrl) {
        if (siteSourceBean == null || StringUtils.isEmpty(chapterDetailUrl)) {
            return Single.create(emitter -> emitter.onError(new Throwable("getBookChapterContent : siteSourceBean is null or chapterListUrl or chapterDetailUrl is empty")));
        }

        try {
            Map<String, String> headers = new HashMap<>();
            headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36");
            return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                    .get(chapterDetailUrl, headers)
                    .timeout(30, TimeUnit.SECONDS)
                    .flatMap(stringResponse -> {
                        return ChapterContentParser.parseBook(siteSourceBean, chapterDetailUrl, stringResponse.body());

                    });
        } catch (Exception e) {
            return Single.create(emitter -> emitter.onError(e));
        }
    }


    public Single<List<ChapterContentBo>> getComicChapterContent(SiteSourceBean siteSourceBean, CollectionMediaBean collectionMediaBean, MediaChapterBean chapter) {
        if (siteSourceBean == null || collectionMediaBean == null) {
            return Single.create(emitter -> emitter.onError(new Throwable("getBookChapterContent : siteSourceBean is null or chapterListUrl or chapterDetailUrl is empty")));
        }
        String chapterId = chapter.getId();
        String link = chapter.getLink();
        if (com.whiteape.book.utils.StringUtils.isAnyEmpty(chapterId, link)) {
            return Single.create(emitter -> emitter.onError(new Throwable("getBookChapterContent : chapterId is null or link is empty")));
        }

        try {
            if (link.toUpperCase().contains("@POST:")) {
                //是post请求的
                String[] arr = link.split("(?i)@POST:");
                String url = arr[0];
                String body = arr[1];
                RequestBody requestBody = RequestBody.create(okhttp3.MediaType.parse("text/plain"), body);

                return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                        .post(url, Collections.emptyMap(), requestBody)
                        .timeout(30, TimeUnit.SECONDS)
                        .flatMap(stringResponse -> {
                            return ChapterContentParser.parseComic(siteSourceBean, collectionMediaBean, chapter, stringResponse.body());

                        });
            }
            return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                    .get(link, Collections.emptyMap())
                    .timeout(30, TimeUnit.SECONDS)
                    .flatMap(stringResponse -> {
                        return ChapterContentParser.parseComic(siteSourceBean, collectionMediaBean, chapter, stringResponse.body());

                    });
        } catch (Exception e) {
            return Single.create(emitter -> emitter.onError(e));
        }
    }

    public Single<List<ChapterContentBo>> getComicContentLazy(SiteSourceBean siteSourceBean, CollectionMediaBean collectionMediaBean, ChapterContentBo chapterContentBo) {

        String link = chapterContentBo.getContent();
        if (siteSourceBean == null || StringUtils.isEmpty(link)) {
            return Single.create(emitter -> emitter.onError(new Throwable("getBookChapterContent : siteSourceBean is null or chapterListUrl or chapterDetailUrl is empty")));
        }

        try {
            return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                    .get(link, Collections.emptyMap())
                    .timeout(30, TimeUnit.SECONDS)
                    .flatMap(stringResponse -> {
//                        return ChapterContentParser.parseOnePageLinkLazy(link, stringResponse.body(), siteSourceBean, chapterContentBo);
                        return ChapterContentParser.parseOnePageLinkLazy(siteSourceBean, collectionMediaBean, chapterContentBo, stringResponse.body());

                    });
        } catch (Exception e) {
            return Single.create(emitter -> emitter.onError(e));
        }
    }

    public Single<List<CategoryDetailBo>> getSourceCategory(SiteSourceBean siteSourceBean, SiteSourceCategoryBean categoryBean, String url) {
        if (siteSourceBean == null || categoryBean == null || categoryBean.getCategoryUrl().isEmpty() || StringUtils.isEmpty(url)) {
            return Single.create(emitter -> emitter.onError(new Throwable("getSourceCategory check args error")));
        }
        try {
            return RetrofitHelper.getInstance().getHttpApiImpl(siteSourceBean.getSiteHostUrl())
                    .get(url, Collections.emptyMap())
                    .timeout(30, TimeUnit.SECONDS)
                    .flatMap(stringResponse -> {
                        return CategoryParser.parse(siteSourceBean, categoryBean, URLUtils.getAbsolutePath(siteSourceBean.getSiteHostUrl(), url), stringResponse.body());
                    });

        } catch (Exception e) {

            return Single.create(emitter -> emitter.onError(e));
        }
    }

    public Observable<Map<SiteSourceBean, List<SiteSourceCategoryBean>>> getSourceCategoryIndex(Set<String> excludeSiteHostUrlSet) {
//        if (siteSourceBean == null) {
//            return Single.create(emitter -> emitter.onError(new Throwable("getSourceCategoryIndex check args error")));
//        }
        //TODO 上面这么一大段逻辑，rx去做
//        Single.create(emitter -> {
//            List<SiteSourceBean> siteSourceBeanList = SiteSourceManager.getInstance().getSiteSourceAll();
//            emitter.onSuccess(siteSourceBeanList);
//        }).compose(RxUtils::toSimpleSingle)
//                .subscribe(o->{
//
//                },Throwable::printStackTrace);

        //判断哪些已经本地存储过了，就不需要再请求了
        List<SiteSourceBean> siteSourceBeanList = SiteSourceManager.getInstance().getSiteSourceExclude(excludeSiteHostUrlSet);
        List<Observable<Map<SiteSourceBean, List<SiteSourceCategoryBean>>>> searchObservableList = new ArrayList<>(siteSourceBeanList.size());

        for (SiteSourceBean siteSourceBean : siteSourceBeanList) {

            if (siteSourceBean == null) {
                return Observable.create(emitter -> emitter.onError(new Throwable("getSearchInfo:siteSourceBean is null or searchKey is empty")));
            }
            SiteSourceCategoryRuleBean categoryRuleBean = siteSourceBean.getSiteCategory();
            if (categoryRuleBean == null) {
                continue;
            }

            String baseUrl = siteSourceBean.getSiteHostUrl();
            String categoryUrl = categoryRuleBean.getUrl();


            Map<String, String> params = categoryRuleBean.getParams();
            if (null == params) {
                params = new HashMap<>(1);
            }
            Map<String, String> tempParams = new HashMap<>(params.size());
            try {
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    tempParams.put(entry.getKey(), entry.getValue());
                }
                Map<String, String> headers = categoryRuleBean.getHeaders();
                if (headers == null) {
                    headers = new HashMap<>();
                    headers.put("Referer", siteSourceBean.getSiteHostUrl());
                } else if (!headers.containsKey("referer") && !headers.containsKey("Referer")) {
                    headers.put("Referer", siteSourceBean.getSiteHostUrl());

                }
                Observable<Map<SiteSourceBean, List<SiteSourceCategoryBean>>> observable = RetrofitHelper.getInstance().getHttpApiImpl(baseUrl)
                        .getMap(categoryUrl, tempParams, headers)
                        .timeout(30, TimeUnit.SECONDS)
//                        .retry(3)
                        .subscribeOn(scheduler)
                        .flatMap(stringResponse -> CategoryParser.parseCategoryIndex(siteSourceBean, stringResponse.raw().request().url().toString(), stringResponse.body()))
                        .toObservable();
                searchObservableList.add(observable);

            } catch (Exception e) {
                e.printStackTrace();
                return Observable.create(emitter -> emitter.onError(new Throwable(e)));
            }
        }
        return Observable.mergeDelayError(searchObservableList);

    }

    public Single<List<SiteSourceHomepageBean>> getHomepageData(SiteSourceBean siteSourceBean) {
        if (siteSourceBean == null) {
            return Single.create(emitter -> emitter.onError(new Throwable("getHomepageData siteSourceBean is null")));
        }
        String url = siteSourceBean.getSiteHostUrl();
        return RetrofitHelper.getInstance().getHttpApiImpl(URLUtils.getBaseUrl(url))
                .get(url, new HashMap<>())
                .timeout(30, TimeUnit.SECONDS)
                .flatMap(stringResponse -> HomepageParser.parse(siteSourceBean, url, stringResponse.body()));


    }

    public Observable<List<CategoryDetailBo>> getHomepageData(SiteSourceBean siteSourceBean, List<SiteSourceCategoryBean> siteSourceCategoryBeanList) {
        if (siteSourceCategoryBeanList == null || siteSourceCategoryBeanList.isEmpty()) {
            return Observable.create(emitter -> emitter.onError(new Throwable("getHomepageData siteSourceCategoryBeanList is null or empty")));
        }
        //最多获取10个分类下的数据来填充首页，太多了没必要
        int count = 0;
        try {
            List<Observable<List<CategoryDetailBo>>> observableList = new ArrayList<>();
            for (SiteSourceCategoryBean categoryBean : siteSourceCategoryBeanList) {
                if (count++ > 30) {
                    break;
                }
                Observable<List<CategoryDetailBo>> observable = getSourceCategory(siteSourceBean, categoryBean, categoryBean.getCategoryUrl()).toObservable();
                observableList.add(observable);
            }
            return Observable.mergeDelayError(observableList);
        } catch (Exception e) {

            return Observable.create(emitter -> emitter.onError(e));
        }

    }

}
